/*

* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Smart-Shop开源商城系统/ Java商城-首选电商平台系统 请务必保留此注释头信息
* |     开源地址: https://gitee.com/ningbo-qishan/gruul-mall
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     可正常分享和学习源码,未经授权 不可商用！
* |     商业版中台购买/开源版商业授权 联系技术客服
* |     官网:  https://www.73app.cn/
* ------------------------------------------------------------------------
* ------------------------------------------------------------------------
* |     Copyright (c) 2016-2999 宁波启山智软科技有限公司
* |     版权所有 ,侵权必究！
* ------------------------------------------------------------------------

*/
package com.medusa.gruul.payment.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.medusa.gruul.common.core.exception.ServiceException;
import com.medusa.gruul.common.core.util.Result;
import com.medusa.gruul.payment.api.constant.ReturnCodeConstant;
import com.medusa.gruul.payment.api.entity.Payment;
import com.medusa.gruul.payment.api.entity.PaymentRecord;
import com.medusa.gruul.payment.api.entity.PaymentWechat;
import com.medusa.gruul.payment.api.enums.*;
import com.medusa.gruul.payment.api.model.dto.PayRequestDto;
import com.medusa.gruul.payment.api.model.dto.PayResultDto;
import com.medusa.gruul.payment.api.model.dto.SxfPayResultDto;
import com.medusa.gruul.payment.api.model.dto.WxPayResultDto;
import com.medusa.gruul.payment.api.model.param.sxf.*;
import com.medusa.gruul.payment.api.util.GlobalConstant;
import com.medusa.gruul.payment.api.util.GlobalTools;
import com.medusa.gruul.payment.conf.PayProperty;
import com.medusa.gruul.payment.handler.AbstractHandler;
import com.medusa.gruul.payment.handler.WxPayMiniHandler;
import com.medusa.gruul.payment.service.*;
import com.medusa.gruul.platform.api.entity.MiniInfo;
import com.medusa.gruul.platform.api.feign.RemoteMiniInfoService;
import com.medusa.gruul.platform.api.model.dto.ShopConfigDto;
import com.medusa.gruul.platform.api.model.vo.PayInfoVo;
import lombok.extern.java.Log;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 * @author create by zq
 * @date created in 2019/11/18
 */
@Log
@Service(value = "sxfPayServiceImpl")
public class SxfPayServiceImpl extends AbstractHandler implements SxfPayService {

    @Autowired
    private RemoteMiniInfoService remoteMiniInfoService;

    @Autowired
    private PayProperty payProperty;

    @Autowired
    private IPaymentService paymentService;

    @Autowired
    private IPaymentWechatService paymentWechatService;

    @Autowired
    private IPaymentRecordService paymentRecordService;

    @Autowired
    private WxPayMiniHandler wxPayMiniHandler;

    @Autowired
    private IPaymentService iPaymentService;


    /**
     * sxf 预支付
     *
     * @param payRequestDto
     * @return PayResultDto
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public PayResultDto handle(PayRequestDto payRequestDto) {
        PayResultDto test = this.test(payRequestDto);
        /** 校验指定模式下的数据是否为空 */
        if (test != null) {
            return test;
        }

        if (StringUtils.isBlank(payRequestDto.getTenantId())) {
            throw new ServiceException("商户唯一标识不存在");
        }

        ShopConfigDto shopConfig = remoteMiniInfoService.getShopConfig(payRequestDto.getTenantId());
        if (shopConfig == null) {
            throw new ServiceException("商户配置不存在");
        }

        PayInfoVo payInfo = shopConfig.getPayInfo();
        if (payInfo == null) {
            throw new ServiceException("支付配置不存在");
        }

        MiniInfo miniInfo = shopConfig.getMiniInfo();
        if (miniInfo == null) {
            throw new ServiceException("未授权小程序");
        }

        payRequestDto.setFeeType(FeeTypeEnum.CNY.getName());
        payRequestDto.setPayChannel(PayChannelEnum.SXF.getType());

        /** 记录调用信息 */
        Payment payment = this.generatePayment(payRequestDto, payProperty.getSxfWorkerId(), payProperty.getSxfDatacenterId());
        paymentService.save(payment);

        /** 记录渠道信息数据 */
        innerHandleChannelData(payment, payRequestDto);

        /** 记录业务数据 */
        PaymentRecord paymentRecord = this.generateRecod(payRequestDto);
        paymentRecord.setPaymentId(payment.getId());

        // 封装SxfWxPlatformPay
        SxfWxPlatformPay sxfWxPlatformPay = innerHandleSxfWxPlatformPay(payRequestDto, payment, payInfo, miniInfo);
        sxfWxPlatformPay.setSubject("默认订单标题");
        // 封装SxfRequestPay
        String data = JSONObject.toJSONString(innerHandleSxfRequestPay(sxfWxPlatformPay, payInfo));
        log.info(String.format("SXF 加密前JSON data: %s", data));
        HashMap reqMap = JSON.parseObject(data, LinkedHashMap.class, Feature.OrderedField);
        String signContent = HttpUtil.toParams(reqMap);
        log.info("SXF order param data json string：" + signContent);

        paymentRecord.setSendParam(String.format("SXF 业务参数加密前请求数据 : %s, sxfRequestPay 加密前数据 : %s",
                JSON.toJSONString(sxfWxPlatformPay), data));
        paymentRecordService.save(paymentRecord);

        String sign = GlobalTools.encryptBase64(GlobalTools.sxfSign(signContent, GlobalConstant.STRING_SXF_PRIVATE_KEY));
        log.info(String.format("SXF 加密后sign: %s", sign));
        reqMap.put(GlobalConstant.STRING_SIGN, sign);
        String reqStr = JSON.toJSONString(reqMap);
        log.info(String.format("SXF 请求参数 data: %s", reqStr));
        String resultJson = null;
        SxfPayResultDto sxfPayResultDto;
        try {
            resultJson = HttpUtil.post(payProperty.getSxfJsApiPayUrl(), reqStr);
            SxfResponsePay responsePay = innerHandlePayData(resultJson);
            sxfPayResultDto = JSONObject.parseObject(JSONObject.toJSONString(responsePay.getRespData()), SxfPayResultDto.class);
            if (null == sxfPayResultDto) {
                throw new ServiceException(String.format("获取 SXF 返回 RespData 数据为空! resultJson: %s", resultJson));
            }
        } catch (Exception e) {
            paymentRecord.setNotifyParam(JSONObject.toJSONString(e.getMessage()));
            throw new ServiceException(String.format("请求 SXF 发起预支付异常, 异常信息: %s", e));
        } finally {
            paymentRecord.setNotifyParam(resultJson);
            paymentRecordService.updateById(paymentRecord);
        }

        return innerHandleResponseData(sxfPayResultDto, payment);
    }


    /**
     * sxf第三方回调入口
     *
     * @param jsonString
     * @return str
     */
    @Override
    public String sxfNotify(String jsonString) {
        log.info(String.format("sxfNotify 返回参数: %s", jsonString));
        try {
            // 字段属性调整序列 与sxf解析一致
            HashMap<String, Object> result = JSON.parseObject(jsonString, LinkedHashMap.class, Feature.OrderedField);
            final String bizCode = "0000";
            final String bizCodeString = "bizCode";
            if (bizCode.equals(result.get(bizCodeString))) {
                String signResult = result.get(GlobalConstant.STRING_SIGN).toString();
                result.remove(GlobalConstant.STRING_SIGN);
                String resultStr = HttpUtil.toParams(result);
                log.info("sxfNotify 拼接参数" + resultStr);
                if (GlobalTools.doSxfCheck(resultStr, signResult, GlobalConstant.STRING_SXF_PUBLIC_KEY)) {
                    log.info("验签成功");
                    SxfCallBack responsePay = BeanUtil.mapToBean(result, new SxfCallBack().getClass(), Boolean.TRUE);
                    Payment payment = iPaymentService.getBaseMapper().selectOne(new QueryWrapper<Payment>().eq("transaction_id", responsePay.getOrdNo()));
                    // 回调处理响应体
                    innerHandleNotifyResponse(payment, jsonString);
                    return paymentService.innerHandleCallback(payment, responsePay.toString());
                } else {
                    log.info(String.format("验签失败, resultStr : %s, signResult: %s, key : %s", resultStr, result, GlobalConstant.STRING_SXF_PUBLIC_KEY));
                }
            }
        } catch (Throwable ex) {
            log.warning("sxfNotify 回调异常 异常信息: " + ex.getMessage());
            return ReturnCodeConstant.FAIL;
        }
        return ReturnCodeConstant.FAIL;
    }


    /**
     * sxf商户配置
     *
     * @param param
     * @return result
     */
    @Override
    public Result sxfMerchantConfig(SxfMerchantConfigParam param) {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        String tenantId = null;

        if (null != requestAttributes) {
            HttpServletRequest request = requestAttributes.getRequest();
            tenantId = request.getHeader(GlobalConstant.STRING_TENANT_ID);
        }

        ShopConfigDto shopConfig = remoteMiniInfoService.getShopConfig(tenantId);
        if (shopConfig == null) {
            throw new ServiceException("商户配置不存在");
        }

        PayInfoVo payInfo = shopConfig.getPayInfo();
        if (payInfo == null) {
            throw new ServiceException("支付配置不存在");
        }

        param.setMno(payInfo.getSxfAccCode());

        // 封装SxfRequestPay
        String data = JSONObject.toJSONString(innerHandleSxfRequestPay(param, payInfo));
        log.info(String.format("SXF 加密前JSON data: %s", data));
        HashMap reqMap = JSON.parseObject(data, LinkedHashMap.class, Feature.OrderedField);
        String signContent = HttpUtil.toParams(reqMap);
        log.info("SXF config param data json string：" + signContent);

        String sign = GlobalTools.encryptBase64(GlobalTools.sxfSign(signContent, GlobalConstant.STRING_SXF_PRIVATE_KEY));
        log.info(String.format("SXF 加密后sign: %s", sign));
        reqMap.put(GlobalConstant.STRING_SIGN, sign);
        String reqStr = JSON.toJSONString(reqMap);
        log.info(String.format("SXF 请求参数 data: %s", reqStr));

        String resultJson =
                HttpUtil.post(GlobalConstant.STRING_SXF_MERCHANT_URL, reqStr);

        return Result.ok(resultJson);
    }


    /**
     * 处理回调响应
     *
     * @param payment
     * @param decryptData
     */
    private void innerHandleNotifyResponse(Payment payment, String decryptData) {
        PaymentRecord record = paymentRecordService.getBaseMapper().selectOne(new QueryWrapper<PaymentRecord>().eq("payment_id", payment.getId()));
        record.setNotifyParam(decryptData);
        paymentRecordService.getBaseMapper().updateById(record);
    }


    /**
     * 验证sxf数据
     *
     * @param resultJson
     * @return SxfResponsePay
     */
    private SxfResponsePay innerHandlePayData(String resultJson) {
        HashMap<String, Object> result = JSON.parseObject(resultJson, LinkedHashMap.class, Feature.OrderedField);
        if (GlobalConstant.STRING_0000.equals(result.get(GlobalConstant.STRING_CODE))) {
            String signResult = result.get(GlobalConstant.STRING_SIGN).toString();
            result.remove(GlobalConstant.STRING_SIGN);
            String resultStr = HttpUtil.toParams(result);
            log.info(String.format("SXF 同步回调数据 data : %s", resultStr));
            if (!GlobalTools.doSxfCheck(resultStr, signResult, GlobalConstant.STRING_SXF_PUBLIC_KEY)) {
                throw new ServiceException(String.format("SXF 返回数据验证不匹配! data: %s, sign: %s, signResult : %s",
                        resultStr, GlobalConstant.STRING_SXF_PRIVATE_KEY, signResult));
            }
            return BeanUtil.mapToBean(result, new SxfResponsePay<JSONObject>().getClass(), Boolean.TRUE);
        }

        throw new ServiceException(String.format("SXF 返回数据Code验证不匹配! data: %s, code 非 0000.", resultJson));
    }


    /**
     * 支付渠道信息封装保存
     *
     * @param payment
     * @param payRequestDto
     */
    private void innerHandleChannelData(Payment payment, PayRequestDto payRequestDto) {
        PaymentWechat paymentWechat = new PaymentWechat();
        paymentWechat.setPaymentId(payment.getId());
        paymentWechat.setTradeType(WxPayEnum.IPS_JS_API.getType());
        paymentWechat.setSubject(payRequestDto.getSubject());
        paymentWechat.setOutTradeNo(payRequestDto.getOutTradeNo());
        paymentWechat.setTenantId(payRequestDto.getTenantId());
        paymentWechat.setOpenId(payRequestDto.getOpenId());
        paymentWechatService.save(paymentWechat);
    }


    /**
     * 封装支付结果
     *
     * @param sxfPayResultDto
     * @param payment
     * @return PayResultDto
     */
    private PayResultDto innerHandleResponseData(SxfPayResultDto sxfPayResultDto, Payment payment) {
        PayResultDto test = new PayResultDto();
        WxPayResultDto wxPayResultDto = new WxPayResultDto();
        wxPayResultDto.setAppId(sxfPayResultDto.getPayAppId());
        wxPayResultDto.setNonceStr(sxfPayResultDto.getPaynonceStr());
        wxPayResultDto.setPackageValue(sxfPayResultDto.getPayPackage());
        wxPayResultDto.setPaySign(sxfPayResultDto.getPaySign());
        wxPayResultDto.setSignType(sxfPayResultDto.getPaySignType());
        wxPayResultDto.setTimeStamp(sxfPayResultDto.getPayTimeStamp());
        wxPayResultDto.setResultCode(GlobalConstant.STRING_SUCCESS);
        wxPayResultDto.setTradeType(WxPayEnum.JSAPI_MINI.toString());
        wxPayResultDto.setTransactionId(payment.getTransactionId().toString());
        wxPayMiniHandler.transactionCloseTime(payment);
        test.setWxResult(wxPayResultDto);
        return test;
    }


    /**
     * 封装SxfRequestPay
     *
     * @param data
     * @param payInfo
     * @return
     */
    private SxfRequestPay innerHandleSxfRequestPay(Object data, PayInfoVo payInfo) {
        SxfRequestPay requestPay = new SxfRequestPay();
        requestPay.setReqData(data);
        requestPay.setVersion(payProperty.getSxfVersion());
        requestPay.setSignType(GlobalConstant.STRING_RSA);
        requestPay.setOrgId(payInfo.getSxfOrgId());
        requestPay.setReqId(UUID.randomUUID().toString().replace(
                GlobalConstant.STRING_BAR, GlobalConstant.STRING_EMPTY));
        requestPay.setTimestamp(new SimpleDateFormat(GlobalConstant.STRING_YYYYMMDDHHMMSS).format(new Date()));
        return requestPay;
    }


    /**
     * 封装SxfWxPlatformPay
     *
     * @param payRequestDto
     * @param payment
     * @param payInfo
     * @param miniInfo
     * @return
     */
    private SxfWxPlatformPay innerHandleSxfWxPlatformPay(PayRequestDto payRequestDto, Payment payment, PayInfoVo payInfo, MiniInfo miniInfo) {
        if (null == miniInfo || StringUtils.isBlank(miniInfo.getAppId())) {
            throw new ServiceException("商户不存在");
        }
        SxfWxPlatformPay platformPay = new SxfWxPlatformPay();
        platformPay.setOrdNo(payment.getTransactionId().toString());
        platformPay.setMno(payInfo.getSxfAccCode());
        platformPay.setAmt(payRequestDto.getTotalFee().toString());
        platformPay.setPayType(SxfPayTypeEnum.WE_CHAT.getType());
        platformPay.setPayWay(WxPayEnum.SXF_JS_API.getCode());
        platformPay.setTimeExpire(String.valueOf(innerHandleExTime(payRequestDto.getTimeoutExpress())));
        platformPay.setSubject(payRequestDto.getSubject());
        platformPay.setTradeSource(SxfTradeSourceEnum.SXF_SERVICE.getType());
        platformPay.setTrmIp(payRequestDto.getTerminalIp());
        platformPay.setUserId(payRequestDto.getOpenId());
        platformPay.setNotifyUrl(payProperty.getSxfNotify());
        platformPay.setSubAppid(miniInfo.getAppId());
        return platformPay;
    }


    /**
     * m-分钟，h-小时，d-天， 1m 24h 1d
     *
     * @param timeoutExpress 超时时间
     * @return
     */
    private Integer innerHandleExTime(String timeoutExpress) {
        if (StrUtil.isNotEmpty(timeoutExpress)) {
            int maxMin = 1440;
            int index = timeoutExpress.length() - 1;
            if (index < 1) {
                return 15;
            }
            //获取数值
            Integer time = Integer.valueOf(StrUtil.subWithLength(timeoutExpress, 0, index));
            //获取单位
            String unit = StrUtil.subWithLength(timeoutExpress, index, 1);
            //m-分钟，h-小时，d-天，
            switch (unit) {
                case "m":
                    if (time > maxMin) {
                        return maxMin;
                    }
                    return time;
                case "h":
                    int min = time * 60;
                    if (min > maxMin) {
                        return maxMin;
                    }
                    return min;
                case "d":
                    if (time > 1) {
                        return maxMin;
                    }
                    return time * 24 * 60;
                default:
                    return 15;
            }
        } else {
            return 15;
        }
    }

}
